package show.me.the.money.dictionary;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.dom4j.Document;
import org.dom4j.Element;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.web.context.WebApplicationContext;

import show.me.the.money.config.SortComparator;
import show.me.the.money.config.Sortable;
import show.me.the.money.util.AppContextHolder;
import show.me.the.money.util.context.Context;
import show.me.the.money.util.exp.ExpRunner;


public class TableDictionary extends Dictionary{
	private static final Log logger = LogFactory.getLog(TableDictionary.class);
	private String fileName;
	private String entryName;
	private String keyField;
	private String textField;
	private String searchField;
	private String searchFieldEx;
	private String sortField;
	private String where = "";
	private boolean queryOnly;
	
	
	@SuppressWarnings("unchecked")
	public void setDefineDoc(Document doc) {
		super.setDefineDoc(doc);
		if(doc == null){
			logger.error("init failed,can't get dic define from" + fileName);
			return;
		}
		Element define = defineDoc.getRootElement();
		
		if(define  == null){
			return;
		}
		entryName = define.attributeValue("entry","");
		keyField = define.attributeValue("keyField","id");
		textField = define.attributeValue("textField","text");
		searchField = define.attributeValue("searchField",textField);
		searchFieldEx = define.attributeValue("searchFieldEx");
		sortField = define.attributeValue("sortField");
		String filter = define.attributeValue("filter","");
		
		if(filter.length() > 0){
			filter=filter.replaceAll("'", "\"");
			try {
				ObjectMapper mapper = new ObjectMapper();
				List exp =  mapper.readValue(filter, List.class);
				where  = " where " + ExpRunner.toString(exp,new Context());;
			} 
			catch (Exception e) {
				logger.fatal("get filter error:", e);
			}
		}
		
		if (sortField != null) {
			where += " order by " + sortField; 
		}
		
		queryOnly = Boolean.parseBoolean(define.attributeValue("queryOnly","false"));
		if(entryName.length() == 0){
			logger.error("init failed,can't find [entry] from table dic file " + fileName);
			return;
		}
		
		List ls = define.elements();
		String[] propFields = new String[ls.size()];
		StringBuffer props = new StringBuffer();
		for(int i = 0;i < ls.size();i ++){
			Element el = (Element)ls.get(i);
			String fld = el.attributeValue("field");
			propFields[i] = fld;
			props.append(",").append(fld);
		}
		
		WebApplicationContext wac = AppContextHolder.get();
		
		if(wac == null){
			logger.error("init failed,WebApplicationContext is null,table dic file " + fileName);
			return;
		}
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = sf.openSession();
		try{
			StringBuffer sql = new StringBuffer();
			sql.append("select ").append(keyField).append(",").append(textField)
			.append(props).append(" from ").append(entryName)
			.append(where);
 
			Query q = ss.createQuery(sql.toString());
			List<Object[]> records = q.list();
 
			int i = 0;
			for(Iterator<Object[]> it = records.iterator(); it.hasNext(); i ++){
				Object[] r = it.next();
				String key = String.valueOf(r[0]);
				String text = String.valueOf(r[1]);				
				DictionaryItem dictionaryItem = new DictionaryItem(key,text);
				dictionaryItem.setIndex(i);
				for(int j = 0; j < propFields.length; j ++){
					if(r[j + 2] != null){
						dictionaryItem.setProperty(propFields[j], String.valueOf(r[j + 2]));
					}
				}
				items.put(key,dictionaryItem);
			}
		}
		catch(Exception e){
			logger.error("init failed,from table dic file " + fileName + ", db error \r ", e);
		}
		finally{
			if(ss.isOpen()){
				ss.close();
			}
		}
	}

	public List<DictionaryItem> getSlice(String parentKey,int sliceType,String queryText,int start,int limit) {
		if(StringUtils.isEmpty(queryText)){
			if(!queryOnly){
				List<DictionaryItem> ls = new ArrayList<DictionaryItem>();
				ls.addAll(items.values());
				Comparator<Sortable> c = new SortComparator();
				Collections.sort(ls, c);
				return ls;
			}
			return null;
		}
		else{
			return query(parentKey,queryText,start,limit);
		}
	}

	@SuppressWarnings("unchecked")
	private List<DictionaryItem> query(String parentKey,String qs,int start,int limit) {
		WebApplicationContext wac = AppContextHolder.get();
		if(wac == null){
			return null;
		}
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = sf.openSession();
		List<DictionaryItem> ls = new ArrayList<DictionaryItem>();
		StringBuffer sql = new StringBuffer();
		String curSF = searchField;
		
		if(qs.startsWith("/")){
			curSF = keyField;
			qs = qs.substring(1);
		}
		else if(qs.startsWith(".")){
			curSF = searchFieldEx;
			qs = qs.substring(1);
		}
		try{
			String cnd = StringUtils.isEmpty(where) ? " where " : where + " and ";
			sql.append("select ").append(keyField).append(" from ").append(entryName)
			.append(cnd).append(curSF);
			if(qs.startsWith("=")){
				qs = qs.substring(1);
				sql.append("='").append(qs).append("'");
			}
			else{
				sql.append(" like '").append(qs).append("%'");
			}
			if(!StringUtils.isEmpty(parentKey)){ //in a composeDictionary
				sql.append(" and ").append("substring(").append(searchField).append(",").append(1).append(",").append(parentKey.length())
					.append(")='").append(parentKey).append("'");
			}
			if(where.length() > 0){
				sql.append(" and ").append(where.substring(7)); 
			}
			Query q = ss.createQuery(sql.toString());
			if(limit > 0){
				q.setFirstResult(start);
				q.setMaxResults(limit);
			}
			List records = q.list();
			int rowCount = records.size();
			for(int i = 0;i < rowCount; i ++){
				Object r = records.get(i);
				String key = String.valueOf(r);
				if(!items.containsKey(key)){
					continue;
				}
				ls.add(items.get(key));
			}
			return ls;
		}
		catch(Exception e){
			logger.error("query failed from table dic:", e);
		}
		finally{
			if(ss.isOpen()){
				ss.close();
			}
		}		
		return ls;
	}
	
	public static void reloadDicsByEntryName(String entryName){
		Document defineDoc = Dictionaries.instance().getDefineDoc();
		Element define = defineDoc.getRootElement();
		@SuppressWarnings("unchecked")
		List<Element> els = define.selectNodes("dic[@class='TableDictionary' or @class='ComposeDictionary']");
		for(Element e : els){
			String id = e.attributeValue("id");
			Document doc = Dictionaries.instance().getDic(id).getDefineDoc();
			if(doc == null){
				continue;
			}
			Element root = doc.getRootElement();
			if(entryName.equals(root.attributeValue("entry"))){
				Dictionaries.instance().reload(id);
				logger.info("dic[" + id + "] reload.");
			}
		}		
	}
}
